﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;


namespace NICE.ShopFront
{
    // Base class, need for corect work of method Subscribe

    public abstract class HelpClass                         
    {
        public virtual void Method_Listener(object obj) { }
    }

    /// <summary>
    /// The class Tovar, which is the base product for creating
    /// objects with which it works
    /// </summary>
    
    public class Tovar                                       
    {                                                       
        private int _type;
        private int _price;
        private int _maxSellTime;

        public int Type
        {
            get { return _type; }
            set { _type = value; }
        }
        public int Price
        {
            get { return _price; }
            set { _price = value; }
        }
        public int MaxSellTime
        {
            get { return _maxSellTime; }
            set { _maxSellTime = value; }
        }
        public Tovar(int InType, int InPrice, int InMaxSellTime)
        {
            _type = InType;
            _price = InPrice;
            _maxSellTime = InMaxSellTime;
        }
    }


    public class Manager
    {
        public AutoResetEvent ARE2 = new AutoResetEvent(false);
        public bool Breack = false;
        private bool _sayMeStop = false;


        public class Zvit : HelpClass
        {
            private struct ZvitStruct { public int price; public int sell_time; public int number;};
            // Int - is the Type of tovar, in struct we hold information about tovar <total price,
            // total time of sell, number of sold items >
            private ZvitStruct BufStract;                                                                
            private static StreamWriter ZvitFile;
            Dictionary<int, ZvitStruct> Table = new Dictionary<int, ZvitStruct>(); 
            
            /// <summary>
            /// Method Listening for event "tovar is sold"
            /// If we have record about this type of tovar in Dictionary - update, else - add record to Dict
            /// </summary>
            /// <param name="Tovar">Takes object of tovar as argument </param>
            public override void Method_Listener(object object1)
            {
                Tovar tovar = (Tovar)object1;
                if (Table.ContainsKey((int)tovar.Type))
                { // This section is for updating consisting information
                    lock (Table)
                    { 
                        ZvitStruct BufStract = new ZvitStruct();
                        BufStract.price = (int)tovar.Price + (int)Table[tovar.Type].price;
                        BufStract.sell_time = (int)tovar.MaxSellTime + (int)Table[tovar.Type].sell_time;
                        BufStract.number = 1 + (int)Table[tovar.Type].number;
                        Table.Remove(((int)tovar.Type));
                        Table.Add(((int)tovar.Type), BufStract);
                    }
                   
                }
                else
                { // This section is for adding a new information about sales of tovar
                    lock (Table)
                    {
                    BufStract.price = (int)tovar.Price;
                    BufStract.sell_time = (int)tovar.MaxSellTime;
                    BufStract.number = 1;
                    Table.Add((int)tovar.Type, BufStract);
                    }

                }
            }
            /// <summary>
            /// This metod shows to user an actual result of sales 
            /// </summary>
            public void ShowZvitNow()
            { 
                System.Console.WriteLine("****** Zvit for this period of time. You will find it in: C:/Zvit.txt**********");
                ZvitFile = File.CreateText("C:/Zvit.txt");
                lock (Table)
                {
                    foreach (var table in Table)
                    {
                        ZvitFile.WriteLine("Tovar of type {0} was sold {1} times, for total {2} seconds, total gained {3} $. ", 
                            table.Key, table.Value.number, table.Value.sell_time, table.Value.price);
                    }
                }
                ZvitFile.Close();
                
            }
        }

        public Manager.Zvit ZvitTable = new Manager.Zvit();
        
        /// <summary>
        /// This method is listeneing the keyboard, it serves to 
        /// coordinate the program with user
        /// </summary>
        public void Vuho()
        {
            ConsoleKeyInfo _key = new ConsoleKeyInfo();
            Console.WriteLine("Conrol panel :");
            Console.WriteLine(" - To stop generating new stuff to our market - press Spacebar.");
            Console.WriteLine(" - If you want to see 'Zvit' - press Enter. You will find it in: C:/Zvit.txt ");
            Console.WriteLine(" - Press 'Q' for Exit program");
            while (true)
            {
                _key = System.Console.ReadKey();
                //listening for the key
                if (_key.Key == ConsoleKey.Enter)
                // reaction for enter
                {
                    ZvitTable.ShowZvitNow();
                }
                if (_key.Key == ConsoleKey.Spacebar)
                //reaction for Spacebar
                {
                    _sayMeStop = true;
                    Console.WriteLine("Stopping generating objects of tovar ");
                }
                if (_key.Key == ConsoleKey.Q) 
                // reaction for Q button
                {
                    ARE2.Set();
                }
            }
        }

        public void Creation_Tool(object queue_as_object)
        {
            //Generating new tovars and filling the queue by them
            Console.WriteLine("Store status : Generating new stuff in process");
            Queue queue = (Queue)queue_as_object;
            int counter = 0;
            Random rand = new Random();

            while (_sayMeStop != true)
            {
                lock (queue)
                {
                    queue.Enqueue(new Tovar(rand.Next(1, 10), rand.Next(1, 10), rand.Next(5, 10)));
                }
                counter++;
                if (counter < 6)
                {
                    Thread.Sleep(500); 
                }
                else 
                { 
                    Thread.Sleep(rand.Next(500,5000)); 
                }
            }
            Breack = true;
            Console.WriteLine("********************Generating new stuff ends here!");
        }
    }

    public class Worker
    {
        private ShopFront _bufSF = new ShopFront(); /// Worker should know wth what Shop-front he will work 
        private Manager _man = new Manager(); /// and who will be his manager.
        private object bufer; /// temporary storage for Tovar which take out from Queue

        public Worker(ShopFront _bufSF, Manager _man)
        {
            this._bufSF = _bufSF;
            this._man = _man;
        }

        ///<sumary> 
        ///if Qeueue contains some Tovar, Worker dequeue this Tovar 
        /// from Queue and trying to put on shop-front and if
        /// the manager does not stop work, he wait for next Tovar,
        ///  else he said "No more Tovar will come"
        ///</sumary> 
        ///
        public void Rabotaj(object BufQueue)                            
        {   
            Queue x = (Queue)BufQueue;                                  
            for (; ; )                                                  
            {                                                           
                if (x.Count != 0)
                {
                    lock (x)
                    {
                        bufer = x.Dequeue();
                    }
                    Tovar CurTovar = (Tovar)bufer;
                    Console.WriteLine("Vasya : Otrumanai novui tovar tupy {0}", CurTovar.Type);
                    _bufSF.Check(CurTovar);
                    continue;
                }
                if (_man.Breack == true)
                {
                    Console.WriteLine("No more Tovar will come");
                    Console.ReadKey();
                }
                Thread.Sleep(3000);      
             }
         }
    }
    

    public class ShopFront
    {
        private object Bufer = null; /// Object for storing tovar for compare in GateKepter()
        private AutoResetEvent _are1 = new AutoResetEvent(false); /// Tool for synchronization of threads              
        private ArrayList Prilavok = new ArrayList(); /// ArraList with serve as stall
        public delegate void TovarIsSoldedEventHandler(object obj); /// delegate and ivent to report the sale of Tovar
        public static event TovarIsSoldedEventHandler TovarIsSold;

        /// <summary>
        ///  Method that takes input some object nad if this object
        ///  has suitable type and Method? he will be Subscribate on
        ///  event TovarIsSold;
        /// </summary>

        public void Subscribe(object obj)                               
        {                                                                 
            HelpClass HelpObj = (HelpClass)obj;                          
            ShopFront.TovarIsSold += new ShopFront.TovarIsSoldedEventHandler(HelpObj.Method_Listener);
        }

        /// <summary>
        /// working reverse way to Subscribe
        /// </summary>
        /// <param name="obj"></param>

        public void UnSubscribe(object obj)                              
        {
            HelpClass HelpObj = (HelpClass)obj;
            ShopFront.TovarIsSold -= new ShopFront.TovarIsSoldedEventHandler(HelpObj.Method_Listener);
        }

        /// <summary>
        /// method start to work, when some Tovar was  sold from shop-front, 
        /// if  solded tovar has same type as Tovar in Bufer, he "unlock the gate" 
        /// and passes Tovar is waiting on shop-front
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
 
 
        public object GateKepter(object obj)                             
        {                                                                
            Tovar CurrentTovar = (Tovar)obj;                             
            Tovar BufTovar = (Tovar)Bufer;                               
            if (CurrentTovar.Type == BufTovar.Type)
            {
                _are1.Set();
            }
            return 0;
        }

        /// <summary>
        /// Check, have shop-front tovar of same type as current
        /// </summary>
        /// <param name="CurrentTovar"></param>
 
        public void Check(Tovar CurrentTovar)                            
        {
            Bufer = CurrentTovar;
            lock (Prilavok)
            {
                // if have, "gate are locked" Tovar waites for his turn
                // for start to sell, an new Tovar's aren't come until
                // Tovar of current type will be solded 
                if (Prilavok.Contains(CurrentTovar.Type))                
                {                                                        
                    _are1.WaitOne();                                      
                }                                                         
            }
            // else, Tovar placed on Shop-front and start to sell                                           
            Put_On_ShopFront(CurrentTovar);                             
        }

        /// <summary>
        /// Tovar that has passed check plased to Prilavok, 
        /// and begin selling in new Thread
        /// </summary>
        /// <param name="CurrentTovar"></param>
 

        private void Put_On_ShopFront(Tovar CurrentTovar)                 
        {                                                                
            {
                lock (Prilavok)
                {
                    Prilavok.Add(CurrentTovar.Type);
                }
                Thread NewThread = new Thread(new ParameterizedThreadStart(Start_To_Sell));
                NewThread.Start(CurrentTovar);
            }
        }

        /// <summary>
        /// simulates sale of Tovar, and generate event TovarIsSold for Zvit
        /// </summary>
        /// <param name="obj"></param>
 
        private void Start_To_Sell(object obj)                           
        {                                                                
            Tovar CurTovar = (Tovar)obj;
            Console.WriteLine("ShopFront : Tovar " + CurTovar.Type.ToString() + " is on sale ");
            Thread.Sleep(CurTovar.MaxSellTime * 1000);
            Console.WriteLine("ShopFront : Was sold Tovar {1}, Tovar was {0} sec on sale", CurTovar.MaxSellTime, CurTovar.Type);
            if (TovarIsSold != null)
            {
                TovarIsSold(CurTovar);
            }
            GateKepter(CurTovar);
            lock (Prilavok)
            {
                Prilavok.Remove(CurTovar.Type);
            }
        }
    }

    public class MainClass
    {
        public AutoResetEvent ARE2 = new AutoResetEvent(false); /// need for work of feature "Exit"

        public static void Main()
        {
            Console.WriteLine("     ***   Welcome to our market   ***  ");

            // initialization part

            Manager Manage = new Manager();
            Queue Konveer = new Queue();                                
            ShopFront SF = new ShopFront();
            Worker Vasya = new Worker(SF, Manage);

            SF.Subscribe(Manage.ZvitTable);
            // Subscribing Zvit on TovarIsSold                  

            Thread FillTheKonveer = new Thread(new ParameterizedThreadStart(Manage.Creation_Tool));
            FillTheKonveer.Start(Konveer);
            Thread VuhoThread = new Thread(Manage.Vuho);                 
            VuhoThread.Start();
            // startig control functions of manager in new thread
            // new threads needs for correct technical realization

            Thread.Sleep(3000);
            Console.WriteLine("\n            Trade started\n");
            Thread WorkerThread = new Thread(new ParameterizedThreadStart(Vasya.Rabotaj));
            WorkerThread.Start(Konveer);
            Manage.ARE2.WaitOne();

            Console.WriteLine("\nProgram end\n");
            WorkerThread.Abort();
            FillTheKonveer.Abort();
            VuhoThread.Abort();
            
        }

    }
}
